package com.jt808.decode.utils;

import com.jt808.decode.constant.*;
import com.jt808.decode.model.Jt808Message;
import com.jt808.decode.model.SummaryData;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import lombok.extern.slf4j.Slf4j;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Uplink Transparent Transmission
 * @date 2024-09-26
 */
@Slf4j
public class Message0600Parser {
    /**
     * decoding 0x0600 data
     * @param msg
     * @param msgBody
     * @return
     * @throws Exception
     */
    public static SummaryData parse(Jt808Message msg, ByteBuf msgBody) throws Exception {
        SummaryData summaryData = parseLocation(msgBody);
        summaryData.setDeviceId(msg.getDeviceId());
        summaryData.setHexMsgId(NumberUtil.formatMessageId(msg.getMsgId()));
        summaryData.setMsgFlowId(msg.getMsgFlowId());
        //Processing additional information
        if (msgBody.readableBytes() > 0) {
            parseExtraInfo(msgBody, summaryData);
        }
        return summaryData;
    }

    /**
     * Processing location information
     *
     * @param msgBody
     * @return
     */
    private static SummaryData parseLocation(ByteBuf msgBody) {
        //Transparent Transmission Type
        int msgTpe = msgBody.readUnsignedByte();
        //Data type
        int dataType = msgBody.readUnsignedByte();
        //terminal status
        long status = msgBody.readUnsignedInt();
        //lat
        double lat = NumberUtil.multiply(msgBody.readUnsignedInt(), NumberUtil.COORDINATE_PRECISION);
        //lon
        double lon = NumberUtil.multiply(msgBody.readUnsignedInt(), NumberUtil.COORDINATE_PRECISION);
        //altitude,unit: m
        int altitude = msgBody.readUnsignedShort();
        //speed ,unit: km/h
        double speed = NumberUtil.multiply(msgBody.readUnsignedShort(), NumberUtil.ONE_PRECISION);
        //direction (0~360;0:north)
        int direction = msgBody.readShort();
        //gnss time
        byte[] timeArr = new byte[6];
        msgBody.readBytes(timeArr);
        String bcdTimeStr = ByteBufUtil.hexDump(timeArr);
        ZonedDateTime gpsZonedDateTime = CommonUtil.parseBcdTime(bcdTimeStr);
        //Judge whether the south latitude and west longitude are based on the value of the status bit
        if (NumberUtil.getBitValue(status, 2) == 1) {
            lat = -lat;
        }
        if (NumberUtil.getBitValue(status, 3) == 1) {
            lon = -lon;
        }
        //location package package
        SummaryData summaryData = new SummaryData();
        summaryData.setMsgType(msgTpe);
        summaryData.setDataType(dataType);
        summaryData.setLocationType((status & 0x0002) > 0 ? 1 : 0);
        summaryData.setLat(lat);
        summaryData.setLon(lon);
        summaryData.setAltitude(altitude);
        summaryData.setSpeed(speed);
        summaryData.setDirection(direction);
        DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
        summaryData.setTime(formatter.format(gpsZonedDateTime));
        return summaryData;
    }

    /**
     * Processing additional information
     * @param msgBody
     * @param summaryData
     * @throws Exception
     */
    private static void parseExtraInfo(ByteBuf msgBody, SummaryData summaryData) throws Exception {
        //Additional information
        Map<String,Object> expandMap=new LinkedHashMap<>();
        Map<String,String>  customerMsg=new LinkedHashMap<>();
        ByteBuf extraInfoBuf = null;
        while (msgBody.readableBytes() > 1) {
            int extraInfoId = msgBody.readUnsignedByte();
            int extraInfoLen;
            if(extraInfoId==0xF5||extraInfoId==0xF6){
                extraInfoLen = msgBody.readUnsignedShort();
            }else {
                extraInfoLen = msgBody.readUnsignedByte();
            }
            if (msgBody.readableBytes() < extraInfoLen) {
                break;
            }
            extraInfoBuf = msgBody.readSlice(extraInfoLen);
            if(extraInfoBuf.readableBytes()==0){
                continue;
            }
            switch (extraInfoId) {
                //Number of GNSS positioning satellites
                case 0x31:
                    int gnssSignal=extraInfoBuf.readUnsignedByte();
                    summaryData.setGnssNum(gnssSignal);
                    break;
                //cells information
                case 0xF0:
                    List<String>lbsCells=new LinkedList<>();
                    while (extraInfoBuf.readableBytes()>11) {
                        int mcc = extraInfoBuf.readUnsignedShort();
                        int mnc = extraInfoBuf.readUnsignedShort();
                        //ci
                        long ci = extraInfoBuf.readUnsignedInt();
                        //lac
                        long lac = extraInfoBuf.readUnsignedInt();
                        //RSSI
                        int rssi = extraInfoBuf.readByte();
                        String lbsCell=String.format("%s,%s,%s,%s,%s", mcc, mnc, lac,ci,rssi);
                        lbsCells.add(lbsCell);
                    }
                    summaryData.setLbsCells(lbsCells);
                    break;
                case 0xF1:
                    Map<String,Object> deviceMap = parseDeviceInfo(extraInfoBuf);
                    if(deviceMap==null) {
                        break;
                    }
                    expandMap.put(Jt808ExpandConstant.DEVICE, deviceMap);
                    break;
                case 0xF2:
                    expandMap.put(Jt808ExpandConstant.WIFI, parseWifiList(extraInfoBuf));
                    break;
                case 0xF3:
                    parseBatteryInformation(summaryData,expandMap,extraInfoBuf);
                    break;
                case 0xF4:
                    Map<String,Object> auxiliaryMap = parseAuxiliaryInformation(extraInfoBuf);
                    if(auxiliaryMap==null) {
                        break;
                    }
                    expandMap.put(Jt808ExpandConstant.AUXILIARY, auxiliaryMap);
                    break;
                case 0xF5:
                    List<Map<String,Object>> sensorList = parseSensorList(extraInfoBuf);
                    if(sensorList==null) {
                        break;
                    }
                    expandMap.put(Jt808ExpandConstant.SENSOR, sensorList);
                    break;
                case 0xF6:
                    expandMap.put(Jt808ExpandConstant.BLUETOOTH, parseBluetoothList(extraInfoBuf));
                    break;
                case 0xF7:
                    byte [] customerMsg1Arr= new byte[extraInfoBuf.readableBytes()];
                    extraInfoBuf.readBytes(customerMsg1Arr);
                    customerMsg.put("customerMsg1",ByteBufUtil.hexDump(customerMsg1Arr));
                    break;
                case 0xF8:
                    byte [] customerMsg2Arr= new byte[extraInfoBuf.readableBytes()];
                    extraInfoBuf.readBytes(customerMsg2Arr);
                    customerMsg.put("customerMsg2",ByteBufUtil.hexDump(customerMsg2Arr));
                    break;
                case 0xF9:
                    byte [] customerMsg3Arr= new byte[extraInfoBuf.readableBytes()];
                    extraInfoBuf.readBytes(customerMsg3Arr);
                    customerMsg.put("customerMsg3",ByteBufUtil.hexDump(customerMsg3Arr));
                    break;
                default:
                    break;
            }
        }
        summaryData.setExpandMap(expandMap);
        summaryData.setCustomerMsg(customerMsg);
    }

    /**
     * device information
     * @param extraInfoBuf
     * @return
     */
    private static Map<String,Object> parseDeviceInfo(ByteBuf extraInfoBuf){
        try{
            Map<String,Object> device=new HashMap<>();
            int softwareLen=extraInfoBuf.readUnsignedByte();
            byte[] softwareArr=new byte[softwareLen];
            extraInfoBuf.readBytes(softwareArr);
            byte[] imeiArr=new byte[8];
            extraInfoBuf.readBytes(imeiArr);
            byte[] iccidArr=new byte[10];
            extraInfoBuf.readBytes(iccidArr);
            byte[] deviceTypeArr=new byte[10];
            extraInfoBuf.readBytes(deviceTypeArr);
            device.put(DeviceConstant.SOFTWARE,new String(softwareArr));
            device.put(DeviceConstant.IMEI, ByteBufUtil.hexDump(imeiArr));
            device.put(DeviceConstant.ICCID,ByteBufUtil.hexDump(iccidArr));
            device.put(DeviceConstant.DEVICE_TYPE,new String(deviceTypeArr));
            return device;
        }catch (Exception e){
            log.error("parseDeviceInfo method exception:",e);
            return null;
        }
    }

    /**
     * WIFI information
     * @param extraInfoBuf
     * @return
     */
    private static List<Map<String,Object>> parseWifiList(ByteBuf extraInfoBuf){
        try{
            //屏蔽字
            List<Map<String, Object>> wifiList = new ArrayList<>();
            while (extraInfoBuf.readableBytes() > 6) {
                Map<String, Object> wifiMap = new HashMap<>();
                byte [] macArr= new byte[6];
                extraInfoBuf.readBytes(macArr);
                wifiMap.put(BluetoothConstant.MAC, CommonUtil.handleMacAddress(ByteBufUtil.hexDump(macArr)));
                wifiMap.put(BluetoothConstant.RSSI, extraInfoBuf.readByte());
                wifiList.add(wifiMap);
            }
            return wifiList;
        }catch (Exception e){
            log.error("parseWifiList method exception:",e);
            return null;
        }
    }

    /**
     * Battery information
     * @param summaryData
     * @param expandMap
     * @param extraInfoBuf
     * @return
     */
    private static void parseBatteryInformation(SummaryData summaryData, Map<String,Object> expandMap, ByteBuf extraInfoBuf) {
        try{
            if(extraInfoBuf.readableBytes()>3) {
                double voltage = extraInfoBuf.readUnsignedInt() * 0.001;
                summaryData.setVoltage(voltage);
            }
            if(extraInfoBuf.readableBytes()>0) {
                int chargeStatus = extraInfoBuf.readByte();
                expandMap.put(Jt808ExpandConstant.BATTERY_CHARGE, chargeStatus);
            }
            if(extraInfoBuf.readableBytes()>0) {
                int battery = extraInfoBuf.readUnsignedByte();
                summaryData.setBattery(battery);
            }
        }catch (Exception e){
            log.error("parseBatteryInformation method exception:",e);
        }
    }

    /**
     * expand information
     * @param extraInfoBuf
     * @return
     */
    private static Map<String,Object> parseAuxiliaryInformation(ByteBuf extraInfoBuf){
        try{
            int mask = extraInfoBuf.readUnsignedByte();
            Map<String,Object> auxiliary=new HashMap<>();
            if((mask&0x01)>0){
                auxiliary.put(AuxiliaryConstant.HDOP,extraInfoBuf.readUnsignedShort());
            }
            return auxiliary;
        }catch (Exception e){
            log.error("parseAuxiliaryInformation method exception:",e);
            return null;
        }
    }

    /**
     * Sensor list
     * @param extraInfoBuf
     * @return
     */
    private static List<Map<String,Object>> parseSensorList(ByteBuf extraInfoBuf){
        List<Map<String,Object>> sensorList=new ArrayList<>();
        while (extraInfoBuf.readableBytes()>7) {
            Map<String, Object> sensorMap=new HashMap<>();
            int mask = extraInfoBuf.readUnsignedShort();
            //gnss time
            byte[] timeArr = new byte[6];
            extraInfoBuf.readBytes(timeArr);
            String bcdTimeStr = ByteBufUtil.hexDump(timeArr);
            ZonedDateTime gpsZonedDateTime = CommonUtil.parseBcdTime(bcdTimeStr);
            DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
            sensorMap.put("dateTime",formatter.format(gpsZonedDateTime));
            if ((mask & 0x01) > 0) {
                int light = extraInfoBuf.readUnsignedShort();
                sensorMap.put(SensorConstant.LIGHT, light);
            }
            if ((mask & 0x02) > 0) {
                //temperature
                String temperature = String.format("%.1f",CommonUtil.parseTemperature(extraInfoBuf.readShort()));
                if(!temperature.equals("-1000.0")) {
                    sensorMap.put(SensorConstant.TEMPERATURE, temperature);
                }
                //humidity
                int humidity = extraInfoBuf.readUnsignedShort();
                sensorMap.put(SensorConstant.HUMIDITY, String.format("%.1f",humidity * 0.1));
            }
            if ((mask & 0x04) > 0) {
                String ext_temp = String.format("%.1f",CommonUtil.parseTemperature(extraInfoBuf.readShort()));
                if(!ext_temp.equals("-1000.0")) {
                    sensorMap.put(AuxiliaryConstant.EXT_TEMP, ext_temp);
                }
            }
            if ((mask & 0x08) > 0) {
                //Accelerometer
                int x = extraInfoBuf.readUnsignedShort();
                int y = extraInfoBuf.readUnsignedShort();
                int z = extraInfoBuf.readUnsignedShort();
                x = x > 32768 ? (x - 65536) : x;
                y = y > 32768 ? (y - 65536) : y;
                z = z > 32768 ? (z - 65536) : z;
                sensorMap.put(SensorConstant.ACCELEROMETER, String.format("x:%s,y:%s,z:%s", x, y, z));
            }
            if ((mask & 0x10) > 0) {
                //Pressure
                Integer pressure = extraInfoBuf.readUnsignedShort()*100;
                sensorMap.put(SensorConstant.PRESSURE,pressure);
            }
            sensorList.add(sensorMap);
        }
        return sensorList;
    }

    /**
     * BLE information
     * @param extraInfoBuf
     * @return
     */
    private static List<Map<String,Object>> parseBluetoothList(ByteBuf extraInfoBuf){
        try{
            List<Map<String, Object>> bluetoothList = new ArrayList<>();
            while (extraInfoBuf.readableBytes() > 6) {
                Map<String, Object> bleMap = new HashMap<>();
                byte [] macArr= new byte[6];
                extraInfoBuf.readBytes(macArr);
                bleMap.put(BluetoothConstant.MAC, CommonUtil.handleMacAddress(ByteBufUtil.hexDump(macArr)));
                int deviceType=extraInfoBuf.readUnsignedByte();
                bleMap.put(BluetoothConstant.DEVICE_TYPE,deviceType);
                int bleMask = extraInfoBuf.readUnsignedShort();
                if ((bleMask & 0x0001) > 0) {
                    int hwVersionLen = extraInfoBuf.readUnsignedByte();
                    byte[] hwVersionArr = new byte[hwVersionLen];
                    extraInfoBuf.readBytes(hwVersionArr);
                    String hwVersion = new String(hwVersionArr);
                    bleMap.put(BluetoothConstant.HWVERSION, hwVersion);
                }
                if ((bleMask & 0x0002) > 0) {
                    int fwVersionLen = extraInfoBuf.readUnsignedByte();
                    byte[] fwVersionArr = new byte[fwVersionLen];
                    extraInfoBuf.readBytes(fwVersionArr);
                    String fwVersion = new String(fwVersionArr);
                    bleMap.put(BluetoothConstant.FWVERSION, fwVersion);
                }
                if ((bleMask & 0x0004) > 0) {
                    int swVersionLen = extraInfoBuf.readUnsignedByte();
                    byte[] swVersionArr = new byte[swVersionLen];
                    extraInfoBuf.readBytes(swVersionArr);
                    String swVersion = new String(swVersionArr);
                    bleMap.put(BluetoothConstant.SWVERSION, swVersion);
                }
                if ((bleMask & 0x0008) > 0) {
                    int productModelLen = extraInfoBuf.readUnsignedByte();
                    byte[] productModelArr = new byte[productModelLen];
                    extraInfoBuf.readBytes(productModelArr);
                    String productModel = new String(productModelArr);
                    bleMap.put(BluetoothConstant.PRODUCTMODEL, productModel);
                }
                if ((bleMask & 0x0010) > 0) {
                    int sensorLen=extraInfoBuf.readUnsignedShort();
                    ByteBuf sensorInfoBuf = extraInfoBuf.readSlice(sensorLen);;
                    List<Map<String,Object>> sensorList=new ArrayList<>();
                    while (sensorInfoBuf.readableBytes()>=10) {
                        Map<String,Object> sensorMap=new HashMap<>();
                        byte[] timeArr = new byte[6];
                        sensorInfoBuf.readBytes(timeArr);
                        String bcdTimeStr = ByteBufUtil.hexDump(timeArr);
                        ZonedDateTime gpsZonedDateTime = CommonUtil.parseBcdTime(bcdTimeStr);
                        DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
                        sensorMap.put(BluetoothConstant.TIME, formatter.format(gpsZonedDateTime));
                        String temperature = String.format("%.1f", CommonUtil.parseTemperature(sensorInfoBuf.readShort()));
                        //temperature
                        if (!temperature.equals("-1000.0")) {
                            sensorMap.put(BluetoothConstant.TEMPERATURE, temperature);
                        }
                        //humidity
                        int humidity = sensorInfoBuf.readUnsignedShort();
                        if (humidity != 0x7FFF) {
                            sensorMap.put(BluetoothConstant.HUMIDITY, String.format("%.1f", humidity * 0.1));
                        }
                        sensorList.add(sensorMap);
                    }
                    if(sensorInfoBuf.readableBytes()>0){
                        int dataLen=sensorInfoBuf.readableBytes();
                        byte [] dataArr= new byte[dataLen];
                        sensorInfoBuf.readBytes(dataArr);
                        bleMap.put(BluetoothConstant.DATA, ByteBufUtil.hexDump(dataArr));
                    }
                    bleMap.put(BluetoothConstant.SENSOR, sensorList);
                }
                if ((bleMask & 0x0020) > 0) {
                    int voltage=extraInfoBuf.readUnsignedShort();
                    bleMap.put(BluetoothConstant.VOLTAGE, voltage*0.001);
                }
                bluetoothList.add(bleMap);
            }
            return bluetoothList;
        }catch (Exception e){
            log.error("parseBluetoothList method exception:",e);
            return null;
        }
    }
}
